//=============================================================================
//
//		Copyright (C) 1995 by Paul S. McCarthy and Eric Sunshine.
//				Written by Paul S. McCarthy and Eric Sunshine.
//							All Rights Reserved.
//
//		This notice may not be removed from this source code.
//
//		This object is included in the MiscKit by permission from the authors
//		and its use is governed by the MiscKit license, found in the file
//		"License.rtf" in the MiscKit distribution.	Please refer to that file
//		for a list of all applicable permissions and restrictions.
//		
//=============================================================================
//-----------------------------------------------------------------------------
// MiscTableScrollData.M
//
//		The (DATA) category of MiscTableScroll.	 Implements the management
//		of the cells of the table.
//
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// $Id: MiscTableScrollData.M,v 1.9 96/01/13 23:43:48 zarnuk Exp $
// $Log:		MiscTableScrollData.M,v $
//	Revision 1.9  96/01/13	23:43:48  zarnuk
//	Fixed bug: -lazyCellAt:: was not asking the dataDelegate.
//	Added value-At:: methods.
//	All allocations are now done from [self zone].
//	
//	Revision 1.8  95/10/20	00:13:00  sunshine
//	Was including MiscTableScroll.h and MiscTableCell.h with "" instead of <>.
//	
//	Revision 1.7  95/10/14	16:50:58  zarnuk
//	Fixed bug: -renewRows: was getting the prototype from the wrong slot.
//	
//	Revision 1.6  95/10/08	16:13:38  zarnuk
//	Cell* --> id.
//-----------------------------------------------------------------------------
#import <misckit/MiscTableScroll.h>
#import <misckit/MiscTableCell.h>
#import "MiscTableBorder.h"
extern "Objective-C" {
#import <appkit/Cell.h>
}
extern "C" {
#import <assert.h>
#import <limits.h>
#import <stdlib.h>
#import <string.h>
}

@implementation MiscTableScroll(DATA)

//-----------------------------------------------------------------------------
// - dummyCellAt::
//-----------------------------------------------------------------------------
- dummyCellAt:(int)row :(int)col
	{
	id p = colInfo.border->getPrototype_P( col );
	[self retireCell:p at:row:col];
	[self reviveCell:p at:row:col];
	if ([p respondsTo: @selector(setStringValue:)])
		{
		char buff[64];
		sprintf( buff, "(r%d,c%d)", row, col );
		[p setStringValue:buff];
		}
	return p;
	}


//-----------------------------------------------------------------------------
// - lazyCellAt::
//-----------------------------------------------------------------------------
- lazyCellAt:(int)row :(int)col
	{
	if (delegate != 0 &&
		[delegate respondsTo: @selector(tableScroll:cellAt: :)])
			return [delegate tableScroll:self cellAt:row :col];
	if (dataDelegate != 0 &&
		[dataDelegate respondsTo: @selector(tableScroll:cellAt: :)])
			return [dataDelegate tableScroll:self cellAt:row :col];
	return 0;
	}


//-----------------------------------------------------------------------------
// - eagerCellAt::
//-----------------------------------------------------------------------------
- eagerCellAt:(int)row :(int)col
	{
	return cells[ row * num_cols + col ];
	}


//-----------------------------------------------------------------------------
// - cellAt::
//-----------------------------------------------------------------------------
- cellAt:(int)row :(int)col
	{
	id cell = 0;
	if ((unsigned int) row < (unsigned int) num_rows &&
		(unsigned int) col < (unsigned int) num_cols)
		{
		if (!lazy)
			cell = [self eagerCellAt:row:col];
		else if (![self respondsTo: @selector(isInIB)])
			cell = [self lazyCellAt:row:col];
		else
			cell = [self dummyCellAt:row:col];

		MiscCoord_V vRow = rowInfo.border->physicalToVisual(row);
		MiscCoord_V vCol = colInfo.border->physicalToVisual(col);
		if ([cell respondsTo:@selector(setState:)])
			[cell setState: colInfo.border->selectionSet().contains(vCol) ||
							rowInfo.border->selectionSet().contains(vRow)];
		}
	return cell;
	}


//-----------------------------------------------------------------------------
// tagAt::, intValueAt::, floatValueAt::, doubleValueAt::,
// stringValueAt::, titleAt::, stateAt::
//-----------------------------------------------------------------------------
#define MISC_CELL_VAL( DATA_TYPE, NAME )\
- (DATA_TYPE) NAME##At:(int) r :(int) c\
	{\
	if (lazy)\
		{\
		if (delegate != 0 &&\
			[delegate respondsTo:@selector(tableScroll:NAME##At: :)])\
			return [delegate tableScroll:self NAME##At:r:c];\
		if (dataDelegate != 0 &&\
			[dataDelegate respondsTo:@selector(tableScroll:NAME##At: :)])\
			return [dataDelegate tableScroll:self NAME##At:r:c];\
		}\
	id cell = [self cellAt:r:c];\
	if (cell != 0 && [cell respondsTo:@selector(NAME)])\
		return [cell NAME];\
	return 0;\
	}

MISC_CELL_VAL( int, tag )						// tagAt::
MISC_CELL_VAL( int, intValue )					// intValueAt::
MISC_CELL_VAL( float, floatValue )				// floatValueAt::
MISC_CELL_VAL( double, doubleValue )			// doubleValueAt::
MISC_CELL_VAL( char const*, stringValue )		// stringValueAt::
MISC_CELL_VAL( int, state )						// stateAt::
MISC_CELL_VAL( char const*, title )				// titleAt::
#undef MISC_CELL_VAL


//-----------------------------------------------------------------------------
// - buffCount
//		For lazy-mode tables that perform multiple-buffering.  This can
//		avoid copying when accessing more than one cell at a time (such
//		as during sorting.)
//-----------------------------------------------------------------------------
- (int) buffCount
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScrollBuffCount:)])
		return [delegate tableScrollBuffCount:self];
	if (dataDelegate != 0 &&
		[dataDelegate respondsTo:@selector(tableScrollBuffCount:)])
		return [dataDelegate tableScrollBuffCount:self];
	return 1;
	}


//-----------------------------------------------------------------------------
// - expandIfNeeded
//-----------------------------------------------------------------------------
- (void) expandIfNeeded
	{
	int const num_cells = max_rows * num_cols;
	if (num_cells > max_cells)
		{
		NXZone* const z = [self zone];
		int const nbytes = num_cells * sizeof(*cells);
		if (max_cells == 0)
			cells = (id*) NXZoneMalloc( z, nbytes );
		else
			cells = (id*) NXZoneRealloc( z, cells, nbytes );
		max_cells = num_cells;
		}
	}


//-----------------------------------------------------------------------------
// - lazyInsertColAt:
//-----------------------------------------------------------------------------
- (void) lazyInsertColAt:(int)n
	{
	colInfo.border->insertAt(n,n);		// FIXME: Check this.
	num_cols++;
	}


//-----------------------------------------------------------------------------
// - eagerInsertColAt:
//-----------------------------------------------------------------------------
- (void) eagerInsertColAt:(int)n
	{
	assert( 0 <= n );  assert( n <= num_cols );
	assert( 0 < num_cols );				// lazyInsert must increment num_cols.

	if (max_rows > 0 || num_rows > 0)
		{
		if (max_rows == 0)
			max_rows = num_rows;
		[self expandIfNeeded];
		int const num_new = num_cols;
		int const num_old = num_cols - 1;
		if (num_old > 0)				// Shift old, existing cells.
			{
			int const frag = num_old - n; // #cols right of insert col.
			id* src = cells + (max_rows * num_old) - frag;
			id* dst = cells + (max_rows * num_new) - frag;
			if (frag > 0)				// Last partial row.
				memmove( dst, src, frag * sizeof(*dst) );

			int const nbytes = num_old * sizeof(*dst);
			src -= num_old;
			dst -= num_new;
			while (src > cells)			// All full rows.
				{
				memmove( dst, src, nbytes );
				src -= num_old;
				dst -= num_new;
				}
			}
		int row = 0;
		id const proto = colInfo.border->getPrototype_P(n);
		id* const plim = cells + (max_rows * num_new);
		id* p = cells + n;
		while (p < plim)				// Initialize cells in new column.
			{
			*p = [self reviveCell:[proto copy] at:row++ :n];
			p += num_new;
			}
		}
	}


//-----------------------------------------------------------------------------
// - insertColAt:
//-----------------------------------------------------------------------------
- insertColAt:(int)n
	{
	if (0 <= n && n <= num_cols)
		{
		[self lazyInsertColAt:n];
		if (!lazy)
			[self eagerInsertColAt:n];
		[self constrainSize];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - addCol
//-----------------------------------------------------------------------------
- addCol
	{
	return [self insertColAt: num_cols];
	}


//-----------------------------------------------------------------------------
// - lazyDeleteColAt:
//-----------------------------------------------------------------------------
- (void) lazyDeleteColAt:(int)n
	{
	colInfo.border->deleteAt_P(n);
	num_cols--;
	}


//-----------------------------------------------------------------------------
// - eagerDeleteColAt:
//-----------------------------------------------------------------------------
- (void) eagerDeleteColAt:(int)n
	{
	assert( 0 < num_cols );
	assert( 0 <= n );  assert( n < num_cols );
	if (max_rows > 0)
		{
		int const num_old = num_cols;
		int const num_new = num_cols - 1;
		id* dst = cells + n;
		id* src = dst + 1;
		id* const dst_lim = cells + (num_old * max_rows);
		while (dst < dst_lim)			// Destroy deleted cells.
			{
			[*dst free];
			dst += num_old;
			}
		if (num_new > 0)				// Shift remaining columns.
			{
			dst = cells + n;
			int const nbytes = num_new * sizeof(*dst);
			id* const src_lim = cells + (max_rows - 1) * num_old + n;
			while (src < src_lim)
				{
				memmove( dst, src, nbytes );
				dst += num_new;
				src += num_old;
				}
			if (n < num_new)			// Shift last partial row.
				memmove( dst, src, (num_new - n) * sizeof(*dst) );
			}
		else // (num_new <= 0)
			{
			max_rows = 0;
			}
		}
	}


//-----------------------------------------------------------------------------
// - deleteColAt:
//-----------------------------------------------------------------------------
- deleteColAt:(int)n
	{
	if (0 <= n && n < num_cols)
		{
		if (!lazy)
			[self eagerDeleteColAt:n];
		[self lazyDeleteColAt:n];
		[self constrainSize];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - numCols
//-----------------------------------------------------------------------------
- (int) numCols
	{
	return num_cols;
	}


//-----------------------------------------------------------------------------
// - lazyInsertRowAt:
//-----------------------------------------------------------------------------
- (void) lazyInsertRowAt:(int)n
	{
	rowInfo.border->insertAt(n,n);		// FIXME.  Check this.
	num_rows++;
	}


//-----------------------------------------------------------------------------
// - eagerInsertRowAt:
//-----------------------------------------------------------------------------
- (void) eagerInsertRowAt:(int)n
	{
	assert( 0 < num_rows );				// lazy-insert must increment num_rows
	assert( 0 <= n );  assert( n < num_rows );

	if (num_cols > 0)
		{
		max_rows++;								// Incremental growth.
		assert( num_rows <= max_rows );
		[self expandIfNeeded];

		int const old_max = max_rows - 1;
		id* p = cells + n * num_cols;
		if (n < old_max)						// Shift other rows.
			{
			id* src = p;
			id* dst = src + num_cols;
			memmove( dst, src, (old_max - n) * num_cols * sizeof(*dst) );
			}
		MiscTableBorder* const bp = colInfo.border;
		for (int c = 0;	 c < num_cols;	c++)	// Initialize new cells.
			*p++ = [self reviveCell:[bp->getPrototype_P(c) copy] at:n :c];
		}
	}


//-----------------------------------------------------------------------------
// - insertRowAt:
//-----------------------------------------------------------------------------
- insertRowAt:(int)n
	{
	if (0 <= n && n <= num_rows)
		{
		[self lazyInsertRowAt:n];
		if (!lazy)
			[self eagerInsertRowAt:n];
		[self constrainSize];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - eagerAddRow
//
// NOTE *1*
//		We always initialize new rows when they are created, so if num_rows
//		is less than max_rows, the new row has already been initialized.
//
// NOTE *2*
//		The initialization loop is organized in column-major order because
//		we want to maximize locality of reference for both code and data
//		during the -copy calls.
//-----------------------------------------------------------------------------
- (void) eagerAddRow
	{
	assert( 0 < num_rows );		// lazy insertRowAt: must increment num_rows.
	if (num_cols > 0)
		{
		if (num_rows >= max_rows)						// NOTE *1*
			{
			int const MISC_TS_MAX_ROWS = ((INT_MAX >> 1) + 1);
			assert( num_rows <= MISC_TS_MAX_ROWS );

			int const old_max = max_rows;
			int n = ( max_rows ? max_rows : 16 );
			while (n < num_rows)
				n <<= 1;
			int const new_max = max_rows = n;

			[self expandIfNeeded];
			id* p0 = cells + old_max * num_cols;
			for (int c = 0;	 c < num_cols;	c++)		// NOTE *2*
				{
				id const proto = colInfo.border->getPrototype_P(c);
				id* p = p0++;
				id* const plim = p + (new_max - old_max) * num_cols;
				while (p < plim)
					{
					*p = [proto copy];					// Initialize new cells.
					p += num_cols;
					}
				}
			}
		int const row = num_rows - 1;
		id* p = cells + (num_cols * row);
		for (int col = 0;  col < num_cols;	col++)
			*p++ = [self reviveCell:*p at:row :col];
		}
	}


//-----------------------------------------------------------------------------
// - addRow
//
// NOTE: We explicitly do NOT -constrainSize and -update here, because we
// expect that the user will call this routine many times successively.
//-----------------------------------------------------------------------------
- addRow
	{
	[self lazyInsertRowAt:num_rows];
	if (!lazy)
		[self eagerAddRow];
	return self;
	}


//-----------------------------------------------------------------------------
// - lazyDeleteRowAt:
//-----------------------------------------------------------------------------
- (void) lazyDeleteRowAt:(int)n
	{
	rowInfo.border->deleteAt_P(n);
	num_rows--;
	}


//-----------------------------------------------------------------------------
// - eagerDeleteRowAt:
//-----------------------------------------------------------------------------
- (void) eagerDeleteRowAt:(int)n
	{
	assert( 0 < num_rows );
	assert( 0 <= n );  assert( n < num_rows );
	if (num_cols > 0)
		{
		assert( num_rows <= max_rows );
		max_rows--;
		id* const dst = cells + (n * num_cols );
		id* p = dst;
		id* const plim = p + num_cols;
		while (p < plim)		// Destroy the cells.
			{
			[*p free];
			p++;
			}
		if (n < max_rows)
			memmove( dst, plim, (max_rows - n) * num_cols * sizeof(*dst) );
		}
	}


//-----------------------------------------------------------------------------
// - deleteRowAt:
//-----------------------------------------------------------------------------
- deleteRowAt:(int)n
	{
	if (0 <= n && n < num_rows)
		{
		if (!lazy)
			[self eagerDeleteRowAt:n];
		[self lazyDeleteRowAt:n];
		[self constrainSize];
		[self update];
		return self;
		}
	return 0;
	}


//-----------------------------------------------------------------------------
// - numRows
//-----------------------------------------------------------------------------
- (int) numRows
	{
	return num_rows;
	}


//-----------------------------------------------------------------------------
// - lazyRenewRows:
//-----------------------------------------------------------------------------
- (void) lazyRenewRows:(int)n
	{
	rowInfo.border->setCount(n);
	num_rows = n;
	}


//-----------------------------------------------------------------------------
// - eagerRenewRows:
//
// NOTE *1*
//		Since we initialize all cells when they are allocated, we only have
//		to do extra work if we need to allocate more rows.
//
// NOTE *2*
//		The initialization loop is organized in column-major order to
//		increase locality of reference for both code and data during the
//		call to -copy.
//-----------------------------------------------------------------------------
- (void) eagerRenewRows:(int)n
	{
	assert( 0 <= n );
	if (num_cols > 0)
		{
		int const old_num_rows = num_rows;
		int const new_num_rows = n;
		if (n > max_rows)								// NOTE *1*
			{
			int const max_old = max_rows;
			int const max_new = max_rows = n;
			[self expandIfNeeded];
			id* p0 = cells + max_old * num_cols;
			id* const plim = cells + max_new * num_cols;
			for (int c = 0; c < num_cols; c++)			// NOTE *2*
				{
				id* p = p0++;
				id const proto = colInfo.border->getPrototype_P(c);
				while (p < plim)
					{
					*p = [proto copy];
					p += num_cols;
					}
				}
			}
		if (old_num_rows < new_num_rows)				// Growing
			{
			id* p0 = cells + old_num_rows * num_cols;
			id* const plim = cells + new_num_rows * num_cols;
			for (int c = 0; c < num_cols; c++)			// NOTE *2*
				{
				int r = old_num_rows;
				id* p = p0++;
				while (p < plim)
					{
					*p = [self reviveCell:*p at:r++ :c];
					p += num_cols;
					}
				}
			}
		else if (old_num_rows > new_num_rows)			// Shrinking
			{
			id* p0 = cells + new_num_rows * num_cols;
			id* const plim = cells + old_num_rows * num_cols;
			for (int c = 0; c < num_cols; c++)			// NOTE *2*
				{
				int r = new_num_rows;
				id* p = p0++;
				while (p < plim)
					{
					*p = [self retireCell:*p at:r++ :c];
					p += num_cols;
					}
				}
			}
		}
	}


//-----------------------------------------------------------------------------
// - renewRows:
//-----------------------------------------------------------------------------
- renewRows:(int)n
	{
	[self clearSelection];
	if (0 <= n && n != num_rows)
		{
		if (!lazy)
			[self eagerRenewRows:n];
		[self lazyRenewRows:n];
		}
	[self constrainSize];
	[self update];
	return self;
	}


//-----------------------------------------------------------------------------
// - empty
//-----------------------------------------------------------------------------
- empty
	{
	return [self renewRows:0];
	}


//-----------------------------------------------------------------------------
// - freeCells
//-----------------------------------------------------------------------------
- (void) freeCells
	{
	if (max_rows > 0 && num_cols > 0)
		{
		id* p0 = cells;
		for (int c = 0;	 c < num_cols;	c++)
			{
			id* p = p0++;
			for (int r = 0;	 r < max_rows;	r++)
				{
				[*p free];
				p += num_cols;
				}
			}
		}
	if (cells != 0)
		NXZoneFree( [self zone], cells );
	cells = 0;
	max_cells = 0;
	max_rows = 0;
	}


//-----------------------------------------------------------------------------
// - emptyAndFreeCells
//-----------------------------------------------------------------------------
- emptyAndFreeCells
	{
	[self empty];
	[self freeCells];
	return self;
	}


//-----------------------------------------------------------------------------
// - setLazy:
//-----------------------------------------------------------------------------
- (void) setLazy:(BOOL)flag
	{
	if (lazy != flag)
		{
		lazy = flag;
		if (lazy)
			[self freeCells];
		else
			[self eagerRenewRows:num_rows];
		}
	}


//-----------------------------------------------------------------------------
// - isLazy
//-----------------------------------------------------------------------------
- (BOOL) isLazy
	{
	return lazy;
	}


//-----------------------------------------------------------------------------
// Generic Slot methods
//-----------------------------------------------------------------------------
- addSlot:(MiscBorderType)b
		{ return (b == MISC_COL_BORDER) ? [self addCol] : [self addRow]; }

- border:(MiscBorderType)b insertSlotAt:(int)n
		{ return (b == MISC_COL_BORDER) ?
				[self insertColAt:n] : [self insertRowAt:n]; }

- border:(MiscBorderType)b deleteSlotAt:(int)n
		{ return (b == MISC_COL_BORDER) ?
				[self deleteColAt:n] : [self deleteRowAt:n]; }

- (int) numSlots:(MiscBorderType)b
		{ return (b == MISC_COL_BORDER) ? [self numCols] : [self numRows]; }


//-----------------------------------------------------------------------------
// REVIVE
//-----------------------------------------------------------------------------
- doReviveCell:cell at:(int)row:(int)col
	{
	if (cell != 0)
		{
		if ([cell respondsTo:@selector(setOwner:)])
			[cell setOwner:self];

		if ([cell respondsTo:@selector(setUseOwnerFont:)])
			[cell setUseOwnerFont:YES];
		if ([cell respondsTo:@selector(setUseOwnerTextColor:)])
			[cell setUseOwnerTextColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerBackgroundColor:)])
			[cell setUseOwnerBackgroundColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerHighlightTextColor:)])
			[cell setUseOwnerHighlightTextColor:YES];
		if ([cell respondsTo:@selector(setUseOwnerHighlightBackgroundColor:)])
			[cell setUseOwnerHighlightBackgroundColor:YES];

		if ([cell respondsTo:@selector(setOwnerFont:)])
			[cell setOwnerFont:[self font]];
		else if ([cell respondsTo:@selector(setFont:)])
			[cell setFont:[self font]];

		if ([cell respondsTo:@selector(setOwnerTextColor:)])
			[cell setOwnerTextColor:[self textColor]];
		else if ([cell respondsTo:@selector(setTextColor:)])
			[cell setTextColor:[self textColor]];

		if ([cell respondsTo:@selector(setOwnerBackgroundColor:)])
			[cell setOwnerBackgroundColor:[self backgroundColor]];
		else if ([cell respondsTo:@selector(setBackgroundColor:)])
			[cell setBackgroundColor:[self backgroundColor]];

		if ([cell respondsTo:@selector(setOwnerHighlightTextColor:)])
			[cell setOwnerHighlightTextColor:[self highlightTextColor]];
		else if ([cell respondsTo:@selector(setHighlightTextColor:)])
			[cell setHighlightTextColor:[self highlightTextColor]];

		if ([cell respondsTo:@selector(setOwnerHighlightBackgroundColor:)])
			[cell setOwnerHighlightBackgroundColor:
										[self highlightBackgroundColor]];
		else if ([cell respondsTo:@selector(setHighlightBackgroundColor:)])
			[cell setHighlightBackgroundColor:[self highlightBackgroundColor]];
		}
	return cell;
	}


- reviveCell:cell at:(int)row :(int)col
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:reviveCell:at: :)])
		return [delegate tableScroll:self reviveCell:cell at:row :col];

	if (dataDelegate != 0 &&
		[dataDelegate respondsTo:@selector(tableScroll:reviveCell:at: :)])
		return [dataDelegate tableScroll:self reviveCell:cell at:row :col];

	if (cell != 0 && [cell respondsTo:@selector(tableScroll:reviveAt: :)])
		return [cell tableScroll:self reviveAt:row :col];

	return [self doReviveCell:cell at:row :col];
	}


//-----------------------------------------------------------------------------
// RETIRE
//-----------------------------------------------------------------------------
- doRetireCell:cell at:(int)row:(int)col
	{
	if ([cell respondsTo:@selector(type)] && [cell type] == NX_ICONCELL)
		[cell setIcon:0];
	else if ([cell respondsTo:@selector(setTitle:)])
		[cell setTitle:""];
	else if ([cell respondsTo:@selector(setStringValueNoCopy:)])
		[cell setStringValueNoCopy:""];
	else if ([cell respondsTo:@selector(setStringValue:)])
		[cell setStringValue:""];
	return cell;
	}


- retireCell:cell at:(int)row:(int)col
	{
	if (delegate != 0 &&
		[delegate respondsTo:@selector(tableScroll:retireCell:at: :)])
		return [delegate tableScroll:self retireCell:cell at:row :col];

	if (dataDelegate != 0 &&
		[dataDelegate respondsTo:@selector(tableScroll:retireCell:at: :)])
		return [dataDelegate tableScroll:self retireCell:cell at:row :col];

	if (cell != 0 && [cell respondsTo:@selector(tableScroll:retireAt: :)])
		return [cell tableScroll:self retireAt:row :col];

	return [self doRetireCell:cell at:row :col];
	}

@end
